package com.vinoxm.rss.service;

import com.alibaba.fastjson.JSONObject;
import com.vinoxm.common.base.exception.BusinessException;
import com.vinoxm.common.base.result.BaseResult;
import com.vinoxm.common.base.result.DataResult;
import com.vinoxm.common.base.result.HandleResult;
import com.vinoxm.common.core.service.BaseService;
import com.vinoxm.common.utils.StringUtils;
import com.vinoxm.rss.dao.RssCopyrightDao;
import com.vinoxm.rss.dao.RssDao;
import com.vinoxm.rss.dao.RssLinkDao;
import com.vinoxm.rss.handler.validate.ControllerParamsValidator;
import com.vinoxm.rss.model.*;
import com.vinoxm.rss.redis.RedisClient;
import com.vinoxm.rss.redis.annotation.RssUpdating;
import com.vinoxm.rss.subscribe.SubscribeAdapter;
import com.vinoxm.rss.vo.RssResultVo;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.vinoxm.rss.subscribe.TrackerHandler.torrentHandler;
import static com.vinoxm.rss.subscribe.TrackerHandler.torrentsHandler;


@Service
@Log4j2
public class RssService extends BaseService<RssDao> {

    @Setter(onMethod_ = @Resource)
    private RssResultService rssResultService;

    @Setter(onMethod_ = @Resource)
    private RssTrackerService rssTrackerService;

    @Setter(onMethod_ = @Resource)
    private RssCopyrightDao copyrightDao;

    @Setter(onMethod_ = @Resource)
    private RssLinkDao linkDao;

    @Setter(onMethod_ = @Resource)
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Setter(onMethod_ = @Resource)
    private TransactionDefinition transactionDefinition;

    @Setter(onMethod_ = @Resource)
    private SubscribeAdapter subscribeAdapter;

    @Transactional(rollbackFor = {Exception.class})
    public BaseResult addOneRssSubs(RssSubscribe rssSubscribe) {
        int count = baseDao.insertOne(rssSubscribe);
        if (count == 0) return DataResult.Failed("Add Failed!");
        List<RssLink> link = rssSubscribe.getLink();
        if (link != null && link.size() > 0) linkDao.insertManyWithPid(link, rssSubscribe.getId());
        List<RssCopyright> copyright = rssSubscribe.getCopyright();
        if (copyright != null && copyright.size() > 0) copyrightDao.insertManyWithPid(copyright, rssSubscribe.getId());
        String season = rssSubscribe.getSeason();
        RedisClient.removeRssSubsBySeason(season);
        if (!RedisClient.isSeasonOptionsMember(season)) {
            RedisClient.setSeasonOptions(baseDao.selectRssSubscribeSeason());
        }
        return BaseResult.Success();
    }

    @Transactional(rollbackFor = {Exception.class})
    public BaseResult addManyRssSubs(RssSubscribe[] rssArray) {
        List<RssSubscribe> list = Arrays.asList(rssArray);
        int count = baseDao.insertMany(list);
        if (count > 0) {
            List<RssSubscribe> result = list.stream().filter(r -> r.getId() != null).collect(Collectors.toList());
            // rss link
            List<RssLink> linkList = result.stream().flatMap(r -> r.getLinkWithPid().stream()).collect(Collectors.toList());
            if (linkList.size() > 0) linkDao.insertMany(linkList);
            // rss copyright
            List<RssCopyright> copyrightList = result.stream().flatMap(r -> r.getCopyrightWithPid().stream()).collect(Collectors.toList());
            if (copyrightList.size() > 0) copyrightDao.insertMany(copyrightList);
            // season
            List<String> seasons = result.stream().map(RssSubscribe::getSeason).distinct().collect(Collectors.toList());
            RedisClient.removeRssSubsBySeason(seasons);
            RedisClient.setSeasonOptions(baseDao.selectRssSubscribeSeason());
        }
        return DataResult.Success(JSONObject.parse(String.format("{\"count\": %s}", count)));
    }

    public BaseResult replaceManyRssSubs(RssSubscribe[] rssArray) {
        Map<String, Method> paramsMap = ControllerParamsValidator.getParamsMap();
        boolean containsLink = paramsMap.containsKey("link");
        if (containsLink) paramsMap.remove("link");
        boolean containsCopyright = paramsMap.containsKey("copyright");
        if (containsCopyright) paramsMap.remove("copyright");
        List<String> seasons = Arrays.stream(rssArray).map(RssSubscribe::getSeason).distinct().collect(Collectors.toList());
        // 打开事务管理
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        List<RssSubscribe> results = baseDao.selectRssSubscribeBySeasons(seasons);
        List<Long> linkDelList = new ArrayList<>();
        List<RssLink> linkAddList = new ArrayList<>();
        List<Long> copyrightDelList = new ArrayList<>();
        List<RssCopyright> copyrightAddList = new ArrayList<>();
        try {
            for (RssSubscribe rss : rssArray) {
                RssSubscribe result = rssListContains(results, rss);
                if (result != null) {
                    result.replaceDetail(rss);
                    long subscribeId = result.getId();
                    if (containsLink) {
                        linkDelList.add(subscribeId);
                        List<RssLink> link = rss.getLink();
                        if (link != null && link.size() > 0) {
                            linkAddList.addAll(link.stream().peek(o -> o.setPid(subscribeId)).collect(Collectors.toList()));
                        }
                    }
                    if (containsCopyright) {
                        copyrightDelList.add(subscribeId);
                        List<RssCopyright> copyright = rss.getCopyright();
                        if (copyright != null && copyright.size() > 0) {
                            copyrightAddList.addAll(copyright.stream().peek(o -> o.setPid(subscribeId)).collect(Collectors.toList()));
                        }
                    }
                    if (paramsMap.size() > 0) {
                        HashMap<String, Object> params = new HashMap<>();
                        paramsMap.forEach((k, v) -> {
                            try {
                                params.put(k, v.invoke(rss));
                            } catch (IllegalAccessException | InvocationTargetException e) {
                                // 事务回滚
                                dataSourceTransactionManager.rollback(transactionStatus);
                                e.printStackTrace();
                                throw new BusinessException("Error params get!");
                            }
                        });
                        baseDao.updateOneColumnsById(subscribeId, params);
                    }
                }
            }
            if (linkDelList.size() > 0) linkDao.deleteManyByPids(linkDelList);
            if (copyrightDelList.size() > 0) copyrightDao.deleteManyByPids(copyrightDelList);
            if (linkAddList.size() > 0) linkDao.insertMany(linkAddList);
            if (copyrightAddList.size() > 0) copyrightDao.insertMany(copyrightAddList);
        } catch (Exception e) {
            // 事务回滚
            dataSourceTransactionManager.rollback(transactionStatus);
            e.printStackTrace();
            throw new BusinessException("Replace failed!");
        }
        RedisClient.removeRssSubsBySeason(seasons);
        RedisClient.setSeasonOptions(baseDao.selectRssSubscribeSeason());
        // 事务提交
        dataSourceTransactionManager.commit(transactionStatus);
        return BaseResult.Success();
    }

    private RssSubscribe rssListContains(List<RssSubscribe> list, RssSubscribe rss) {
        if (rss != null && list.size() > 0) {
            for (RssSubscribe rssSubscribe : list) {
                if (StringUtils.equals(rssSubscribe.getName(), rss.getName())) {
                    return rssSubscribe;
                }
            }
        }
        return null;
    }

    public BaseResult delOneRssSubs(RssSubscribe rssSubscribe) {
        Long id = rssSubscribe.getId();
        RssSubscribe subs = baseDao.selectOneById(id);
        if (subs != null) {
            baseDao.deleteOne(rssSubscribe);
            String season = subs.getSeason();
            RedisClient.removeRssSubsBySeason(season);
            RedisClient.setSeasonOptions(baseDao.selectRssSubscribeSeason());
        }
        return BaseResult.Success();
    }

    public BaseResult delOneRssSubsResultsById(RssSubscribe rssSubscribe) {
        RssSubscribe subs = baseDao.selectOneById(rssSubscribe.getId());
        if (subs != null) {
            baseDao.deleteManyRssResultByPid(rssSubscribe.getId());
            RedisClient.removeRssSubsBySeason(subs.getSeason());
        }
        return BaseResult.Success();
    }

    public BaseResult delOneResultById(RssResultVo result, boolean isHide) {
        int count = isHide ? baseDao.hideOneRssResult(result) : baseDao.deleteOneRssResult(result);
        if (count == 1) {
            RedisClient.removeRssSubsBySeason(result.getSeason());
        }
        return BaseResult.Success();
    }

    public BaseResult delManyRssSubs(RssSubscribe[] rssArray) {
        List<RssSubscribe> list = Arrays.asList(rssArray);
        List<String> seasons = baseDao.selectRssSubscribeSeasonByIds(list);
        baseDao.deleteMany(list);
        RedisClient.removeRssSubsBySeason(seasons);
        RedisClient.setSeasonOptions(baseDao.selectRssSubscribeSeason());
        return BaseResult.Success();
    }

    public BaseResult selOneRssSubs(long id, int withOutHide) {
        return DataResult.Success(withOutHide == 0 ? baseDao.selectResultByPid(id) : baseDao.selectResultByPidWithOutHide(id));
    }

    @Transactional
    public BaseResult getOneDetail(long id) {
        JSONObject json = new JSONObject();
        json.put("result", baseDao.selectResultByPidWithOutHide(id));
        json.put("link", linkDao.selectByPid(id));
        json.put("copyright", copyrightDao.selectByPid(id));
        return DataResult.Success(json);
    }

    /**
     * 按订阅名搜索直接从数据库查询
     * 仅季度查询时,则从缓存查询
     * 缓存查询前,先刷新缓存中需要刷新的季度结果缓存
     * 获取所有季度时,需先刷新所有季度的缓存结果
     *
     * @param name:   按RSS订阅名模糊搜索 (nullable),
     * @param season: RSS订阅季度 (nullable)
     * @return 返回符合的结果集
     */
    public BaseResult selSearchRssSubs(String name, String season) {
        List<RssSubscribe> list;
        // 是否是搜索结果
        if (StringUtils.isEmpty(name) && !StringUtils.isEmpty(season)) {
            // 缓存中是否存在该季度RSS
            List<RssSubscribe> l = RedisClient.getRssSubsBySeason(season);
            if (l == null) {
                // 刷新缓存
                list = baseDao.selectRssSubscribeBySeason(season);
                CompletableFuture.runAsync(() -> RedisClient.flushRssSubsBySeason(baseDao.selectRssSubscribeBySeasonOld(season), season));
            } else {
                return DataResult.Success(l);
            }
        } else {
            // 直接查询搜索结果
            list = baseDao.selectAll(name, season);
        }
        return DataResult.Success(list);
    }

    public BaseResult editOneRssSubsById(RssSubscribe rssSubscribe) {
        RssSubscribe old = baseDao.selectOneById(rssSubscribe.getId());
        if (!Objects.equals(old.getUrl(), rssSubscribe.getUrl()) || !Objects.equals(old.getRegex(), rssSubscribe.getRegex())) {
            baseDao.deleteManyRssResultByPid(old.getId());
        }
        baseDao.updateOneById(rssSubscribe);
        String season = rssSubscribe.getSeason();
        RedisClient.removeRssSubsBySeason(season);
        String oldSeason = old.getSeason();
        RedisClient.removeRssSubsBySeason(oldSeason);
        if (!RedisClient.isSeasonOptionsMember(season)) {
            RedisClient.setSeasonOptions(baseDao.selectRssSubscribeSeason());
        }
        return BaseResult.Success();
    }

    public BaseResult selSeason() {
        Set<Object> set = RedisClient.getSeasonOptions();
        if (null != set && set.size() > 0) {
            return DataResult.Success(set);
        } else {
            List<String> list = baseDao.selectRssSubscribeSeason();
            RedisClient.setSeasonOptions(list);
            return DataResult.Success(list);
        }
    }

    @RssUpdating
    public BaseResult updateSubscribe(Long id) {
        RssSubscribe rssSubscribe = baseDao.selectOneById(id);
        if (null == rssSubscribe) return BaseResult.Failed("No such RSS Subscribe found!");
        List<RssResult> results = subscribeAdapter.handleRssSubscribe(rssSubscribe, true);
        if (null == results) return BaseResult.ServerError();
        addManyResult(results);
        return BaseResult.Success();
    }

    @RssUpdating
    public BaseResult updateManySubscribe(List<RssSubscribe> list) {
        if (null == list) return BaseResult.Failed("Empty list!");
        HandleResult<RssResult> result = subscribeAdapter.handleRssSubscribe(list);
        addManyResult(result.completeAndRemove());
        return result;
    }

    public BaseResult selSubscribeWithGoon(String season) {
        return DataResult.Success(baseDao.selectRssSubscribeCauseGoon(season));
    }

    public void addManyResult(List<RssResult> results) {
        if (results.size() > 0) {
            List<RssTracker> trackers = torrentsHandler(results);
            Long maxResultId = baseDao.selMaxResultId();
            List<RssTracker> trackerList = rssTrackerService.saveTrackers(trackers);
            for (int i = 0; i < results.size(); i++) {
                RssResult rssResult = results.get(i);
                rssResult.setId(i + maxResultId + 1);
                List<String> trs = rssResult.getTrackers();
                if (null != trs)
                    rssResult.setTracker(trackerList.stream().filter(t -> trs.contains(t.getHost())).map(t -> t.getId().toString()).collect(Collectors.joining(",")));
                rssResultService.handleRssResultEpisode(rssResult);
            }
            // 大于 splitCounts 条时分批插入
            int rows = 0;
            int splitCounts = 100;
            if (results.size() > splitCounts) {
                int i = 0;
                while (i < results.size()) {
                    results.subList(i, Math.min(i + splitCounts, results.size()));
                    rows += baseDao.insertManyResult(results);
                    i += splitCounts;
                }
            } else {
                rows += baseDao.insertManyResult(results);
            }
            log.info(String.format("Success to update counts: %s", rows));
        }
    }

    public BaseResult setSubscribeFin(RssSubscribe rss) {
        if (baseDao.updateRssSubscribeFin(rss) == 1) {
            String season = rss.getSeason();
            RedisClient.removeRssSubsBySeason(season);
        }
        return BaseResult.Success();
    }

    @Transactional(rollbackFor = {Exception.class})
    public BaseResult addOneRssResult(RssResultVo result) {
        handleOneResultTorrent(result);
        rssResultService.handleRssResultEpisode(result);
        Long maxId = baseDao.selMaxResultId();
        result.setId(maxId + 1);
        int count = baseDao.insertOneResult(result);
        if (count == 1 && !StringUtils.isEmpty(result.getSeason())) {
            RedisClient.removeRssSubsBySeason(result.getSeason());
        }
        return count == 1 ? BaseResult.Success() : BaseResult.Failed("添加失败,已存在");
    }

    @Transactional(rollbackFor = {Exception.class})
    public BaseResult editOneRssResult(RssResultVo result) {
        handleOneResultTorrent(result);
        int count = baseDao.updateOneResultById(result);
        if (count == 1 && !StringUtils.isEmpty(result.getSeason())) {
            RedisClient.removeRssSubsBySeason(result.getSeason());
        }
        return count == 1 ? BaseResult.Success() : BaseResult.Failed("修改失败");
    }

    public BaseResult subscribeRssTest(RssSubscribe rss) {
        List<RssResult> results = subscribeAdapter.handleRssSubscribe(rss, false);
        return results == null ? BaseResult.ServerError() : DataResult.Success(results);
    }

    public List<RssSubscribe> getRssSubscribeForUpdate() {
        return baseDao.selectRssSubscribeWithoutFin();
    }

    public List<RssSubscribe> getManyRssSubscribeForUpdate(Long[] ids) {
        return baseDao.selectManyByIds(Arrays.asList(ids));
    }

    public void handleOneResultTorrent(RssResultVo result) {
        List<RssTracker> trackers = torrentHandler(result);
        if (trackers.size() > 0) {
            List<RssTracker> trackerList = rssTrackerService.saveTrackers(trackers);
            List<String> trs = trackers.stream().map(RssTracker::getHost).collect(Collectors.toList());
            result.setTracker(trackerList.stream().filter(t -> trs.contains(t.getHost())).map(t -> t.getId().toString()).collect(Collectors.joining(",")));
        }
    }

    public BaseResult getSubscribeMatchers() {
        return DataResult.Success(subscribeAdapter.getSubscribeMatchers());
    }
}
